package heap;

import stack.*;

public class Heap {

	/**
	 * Size of my Heap
	 */
	int heapSize = 100000;

	/**
	 * My Heap
	 */
	double[] my_heap;

	/**
	 * My Heap-Pointer, shows the first not allocated cell 
	 */
	int heap_pointer;

	/**
	 * Heap Registry, stores first and last cell-number of an object
	 */
	private HeapRegistry  reg;
	
	/**
	 * Heap-Constructor, initializes die Heap
	 */
	
	public Heap() {
		this.my_heap = new double[this.getHeapSize()];
		for (int i = 0; i < this.my_heap.length; i++) {
			this.my_heap[i] = -1;
		}
		this.heap_pointer = 0;
		reg = new HeapRegistry();
	}

	/**
	 * @return Size of the Heap
	 */
	public int getHeapSize() {
		return this.heapSize;
	}

	/**
	 * @return the current position of the Heap-Pointer
	 */
	public int getHeap_pointer() {
		return this.heap_pointer;
	}

	/**
	 * Takes the first n cells from the Stack and puts them on the heap.
	 * Heap-Pointer increases.
	 * 
	 * @param stack
	 * @param n
	 * @return reference of the newly created Object
	 */
	public int newObject(Stack stack, int n) throws OutOfMemory, Underflow, AdressException {
		int reference = -1;
		if (n < 0) {
			throw new AdressException("[newObject in SO-VM.heap.Heap] Object Adress must be positive");
		}
		if (this.getHeap_pointer() + n >= this.getHeapSize()) {
			throw new OutOfMemory(
					"[newObject in SO-VM.heap.Heap] Memory is full; Size: "
							+ this.getHeapSize());
		}
		reference = this.getHeap_pointer();
		this.heap_pointer = this.getHeap_pointer() + n;
		this.reg.putEndPoint(reference, getHeap_pointer());
		return reference;
	}

	public int getLengthOfAnObject(int reference) {
		return reg.getlengthOfObject(reference);
	}
	
	/**
	 * Empties the complete Heap
	 */
	public void empty() {
		for (int i = 0; i< this.getHeapSize(); i++) {
			this.my_heap[i] = -1;
		}
		this.heap_pointer = 0;
	}

	/**
	 * Tests if a given reference is not Out Of Bounds
	 * @param ref the reference
	 * @throws AdressException
	 */
	public void check(int ref) throws AdressException {
		if (ref<0 || (ref>=this.getHeap_pointer())) {
			throw new AdressException("AdressCheck failed: Fatal Error");
		}
	}
	
	/**
	 * Frees all Heap-Cells between begin and the end of the heap
	 * 
	 * @param begin
	 */
	public void release(int begin) throws AdressException {
		check(begin);
		for (int i = begin; i < this.getHeapSize(); i++) {
			this.my_heap[i] = -1;
		}
		this.heap_pointer = begin;
	}
	
	/**
	 * Returns the value for given parameters
	 * @param reference The reference of the object
	 * @param index Position of the value inside the object
	 * @return value
	 * @throws AdressException
	 */
	public double getValue(int reference, int index) throws AdressException {
		check(reference + index);
		return this.my_heap[reference + index];
	}
	
	/**
	 * Updates the value in an Object
	 * @param reference The reference of the object
	 * @param index The position of the value inside the object
	 * @param value The new value
	 * @throws AdressException
	 */
	public void update(int reference, int index, double value) throws AdressException {
		check(reference + index);
		this.my_heap[reference + index] = value;
	}
}
